package com.saville.mc;

import java.net.Socket;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Locale;

import com.saville.debug.Log;

public class HtmlReq {
    HtmlReq() {
    }
    
    HtmlReq(Socket socket) throws IOException {
        mSocket = socket;
    }
    
    HtmlReq(DataOutputStream os, HashMap<String, String> headers) {
        mOs = os;
        mHeaders = headers;
    }
    
    public void start() throws IOException {
        OutputStream os = mSocket.getOutputStream();
        InputStream is = mSocket.getInputStream(); 
        mIs = new DataInputStream(is);
        mOs = new DataOutputStream(os);
        GetTheRequestAndHeaders();
    }
    
    public void close() throws IOException {
        if (mIs != null) {
            //mIs.close(); // WHAT TO DO ???? mIs = null;
            mIs = null;
        }
        if (mOs != null) {
            //mOs.close(); // WHAT TO DO ???? mOs = null;
            mOs = null;
        }
        if (mHeaders != null) {
            mHeaders = null;
        }
    }
    
    public DataOutputStream getDataOutStream() {
        return mOs;
    }
    
    /*
    public void setDataOutStream(DataOutputStream os) {
        mOs = os;
    }
    */
    
    public String getHeader(String key) {
        return mHeaders.get(key);
    }
    
    /*
    public void setHeaders(HashMap<String, String> headers) {
        mHeaders = headers;
    }
    */
    
    public HashMap<String, String> getHeaders() {
        return mHeaders;
    }
    
    public byte [] getData() {
        return mData;
    }
    
    public int readBody() throws IOException {
        String cls = mHeaders.get("content-length");
        if (cls == null) {
            throw new RuntimeException("No content length");
        }
        int len = Integer.parseInt(cls);
        if (len > MAX_LENGTH) {
            throw new RuntimeException("Length too large");
        }
        if (len > mData.length) {
            mData = new byte[len];
        }
                        
        /* Read the mcid */
        mIs.readFully(mData, 0, len);
        
        return len;
    }
        
    public String readBodyAsString() throws IOException {
        int len = readBody();
        byte data[] = new byte[len]; 
        System.arraycopy(mData, 0, data, 0, len);
        return new String(data);
    }
            
    public void sendResp(int status, String contentType,
                            String headers[], byte [] body) throws IOException {
        StringBuffer responseHeaders = new StringBuffer();
        String statusString;
        
        statusString = mStatusStrings.get(status);
        if (statusString == null) {
            statusString = "unknown status";
        }
        
        responseHeaders.append(String.format("HTTP/1.1 %d %s\r\n", status, statusString));
        
        if (headers != null) {
            for (int i=0; i < headers.length; i++) {
                responseHeaders.append(headers[i]);
            }
        }
        
        int len = (body != null) ? body.length : 0;
        responseHeaders.append(String.format("Content-Length: %d\r\n", len));
        
        if (contentType != null) {
            responseHeaders.append(String.format("Content-Type: %s\r\n", contentType));
        }
        responseHeaders.append("Keep-alive: 300\r\n");
        responseHeaders.append("Connection: keep-alive\r\n");
        responseHeaders.append("\r\n");
        //Log.print("%s: sendResp headers:\n%s", mAjaxMsgPipeName, responseHeaders);
        
        mOs.write(responseHeaders.toString().getBytes());
        if (body != null) {
            mOs.write(body);
        }
        mOs.flush();
    }
    
    public void sendResp(int status, String contentType, String headers[]) throws IOException {
        sendResp(status, contentType, headers, null); 
    }
     
    public void sendResp(int status) throws IOException {
        sendResp(status, null, null, null); 
    }
    
    private byte nextByte() throws IOException {
        byte b = 0;
        
        if (mPushedByteValid) {
            b = mPushedByte;
            mPushedByteValid = false;
        } else {
            b = mIs.readByte();
        }
        return b;
    }
    
    private void pushByte(byte b) {
        mPushedByteValid = true;
        mPushedByte = b;
    }
    byte mPushedByte;
    boolean mPushedByteValid = false;
    
    private String readLine(DataInputStream strm) throws IOException {
        byte b;
        byte nb;
        StringBuffer sb = new StringBuffer();
        
        while(true) {
            b = nextByte();
            
            if (b == '\r') {
                nb = nextByte();
                if (nb != '\n')
                    pushByte(nb);
                break;
            } else if (b == '\n') {
                nb = nextByte();
                if (nb != '\r') {
                    pushByte(nb);
                }
                break;
            }
            sb.append((char)b);
        }
        
        return sb.toString();
    }

    private void addHeader(HashMap<String, String> headers, String key, String value) {
        if (headers.containsKey(key)) {
            StringBuffer valueBuffer = new StringBuffer(headers.get(key));
            valueBuffer.append(",");
            valueBuffer.append(value);
            headers.put(key, valueBuffer.toString());
            //Log.print("more header key='%s' value='%s'", key, valueBuffer.toString());
        } else {
            //Log.print("new header key='%s' value='%s'", key, value);
            headers.put(key, value);
        }
    }
    
    private void addHeader(HashMap<String, String> headers, String line) {
        String kv_pair[] = line.split(":", 2);
        if (kv_pair.length != 2) {
            //Log.e(df, "bad header no ':' line='%s'", line);
        } else {
            String key = kv_pair[0].trim().toLowerCase(Locale.ENGLISH);
            String value = kv_pair[1].trim();
            addHeader(headers, key, value);
        }
    }
    
    private void GetTheRequestAndHeaders() throws IOException {
        StringBuffer prevStringBuffer = new StringBuffer();
        String prevLine = null;
        String line;
                
        /*
         * Read the request and place in headers REQ_METHOD, REQ_URI & REQ_VER
         */
        String request = readLine(mIs);
        //Log.v(df, "request='%s'", request);
        if (request == null)
            throw new RuntimeException("No Request");
        
        if (mHeaders == null) {
            mHeaders = new HashMap<String, String>();
        }
        
        mHeaders.clear();
        String request_tokens[] = request.split(" ");
        if ((request_tokens.length == 2) || (request_tokens.length == 3)) {
            addHeader(mHeaders, "REQ_METHOD", request_tokens[0].trim());
            addHeader(mHeaders, "REQ_URI", request_tokens[1].trim());
            if (request_tokens.length == 3) {
                addHeader(mHeaders, "REQ_VER", request_tokens[2]);
            } else {
                addHeader(mHeaders, "REQ_VER", "HTTP/1.0");
            }
        } else {
            throw new RuntimeException("Bad request='" + request + "'");
        }
                
        /*
         * Read headers
         * TODO: Make subroutine
         */
        while (((line = readLine(mIs)) != null) && (line.length() > 0)) {
            //Log.print("read line='%s'", line);
            char firstCh = line.charAt(0);
            if ((firstCh == ' ') || (firstCh == '\t')) {
                /* TODO: Test continuation */
                if (prevLine != null) {
                    prevStringBuffer.append(line);
                    //Log.print("continuation line='%s'", prevStringBuffer.toString());
                } else {
                    //Log.e(df, "no previous line, illgal continuation line='%s'", line);
                }
            } else {
                if (prevLine != null) {
                    if (prevStringBuffer.length() > 0) {
                        prevLine = prevStringBuffer.toString();
                        prevStringBuffer.setLength(0);
                    }
                    addHeader(mHeaders, prevLine);
                }
                prevLine = line;
            }
        }
        if (prevLine != null) {
            if (prevStringBuffer.length() > 0) {
                prevLine = prevStringBuffer.toString();
                prevStringBuffer.setLength(0);
            }
            addHeader(mHeaders, prevLine);
            prevLine = null;
        }
        //Log.print("headers=%s", headers.toString());
    }   
    /*
    public HashMap<String, String> getHeaders() {
        return mHeaders;
    }
    */
    
     
    HashMap<Integer, String> mStatusStrings = new HashMap<Integer, String>();
    {
        mStatusStrings.put(200, "OK");
        mStatusStrings.put(404, "Not Found");
    }
    
    private int MAX_LENGTH = 65536;
    private byte mData[] = new byte[4096];
    private Socket mSocket;
    private DataInputStream mIs;
    private DataOutputStream mOs;
    private HashMap<String, String> mHeaders;
}
     
